{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Teil 2: Einleitung zum Federated Learning\n",
    "\n",
    "Im letzten Abschnitt wurden PointerTensoren, welche die Infrastruktur für vertrauliches Deep Learning bilden, vorgestellt. In diesem Abschnitt erfahren Sie, wie Sie diese nutzen und Ihren ersten Algorithmus fürs vertrauliche Deep Learning implementieren: Federated Learning.\n",
    "\n",
    "Autoren:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "\n",
    "Übersetzer:\n",
    "- Jan Moritz Behnken - Github: [@JMBehnken](https://github.com/JMBehnken)\n",
    "\n",
    "### Was ist Federated Learning?\n",
    "\n",
    "Es ist eine einfache und mächtige Art und Weise Deep Learning Modele zu trainieren.  \n",
    "Wenn Sie an Trainings-Datensätze denken, dann sind diese immer auch ein Ergebnis eines Sammelprozesses. Menschen (mit ihren Geräten) generieren Daten indem sie Ereignisse in der realen Welt beobachten. Normalerweise werden diese Daten anschließend an einem einzigen, zentralen Ort gesammelt, um anschließend ein Machine Learning Model auf ihnen zu trainieren. Federated Learning dreht diesen Prozess jedoch um!\n",
    "\n",
    "Anstatt die Trainingsdaten zum Model (einem zentralen Server) zu bringen, wird das Model zu den Geräten mit den Trainingsdaten gebracht. \n",
    "\n",
    "Die Idee dahinter ist, dass der Besitzer der Daten die einzige existierende Kopie der Daten behält und er damit selbst entscheiden kann, wem er Zugang gewährt. Ziemlich cool, nicht wahr?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Abschnitt 2.1 - Ein einfaches Federated Learning Beispiel\n",
    "\n",
    "Lassen Sie uns mit dem dezentralen Trainieren eines einfachen Models starten. Das Model wird dabei so grundlegend wie möglich sein.  \n",
    "Dazu brauchen wir:\n",
    "\n",
    "- einen einfachen Datensatz\n",
    "- ein Model\n",
    "- grundlegende Trainings Infrastruktur zum Anpassen des Models an die Daten\n",
    "\n",
    "Anmerkung:  \n",
    "Wenn diese API Ihnen nicht geläufig ist - können Sie unter [fast.ai](http://fast.ai) einen Kurs absolvieren bevor Sie dieses Tutorial fortführen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:35:17.637984Z",
     "start_time": "2020-03-31T11:35:17.333833Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:35:42.345911Z",
     "start_time": "2020-03-31T11:35:42.329493Z"
    }
   },
   "outputs": [],
   "source": [
    "# A Toy Dataset\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]])\n",
    "target = torch.tensor([[0],[0],[1],[1.]])\n",
    "\n",
    "# A Toy Model\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "def train():\n",
    "    # Training Logic\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(20):\n",
    "\n",
    "        # 1) erase previous gradients (if they exist)\n",
    "        opt.zero_grad()\n",
    "\n",
    "        # 2) make a prediction\n",
    "        pred = model(data)\n",
    "\n",
    "        # 3) calculate how much we missed\n",
    "        loss = ((pred - target)**2).sum()\n",
    "\n",
    "        # 4) figure out which weights caused us to miss\n",
    "        loss.backward()\n",
    "\n",
    "        # 5) change those weights\n",
    "        opt.step()\n",
    "\n",
    "        # 6) print our progress\n",
    "        print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:35:42.619709Z",
     "start_time": "2020-03-31T11:35:42.572645Z"
    }
   },
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So weit, so gut! Wir haben ein einfaches Model auf die konventionelle Art trainiert. Alle unsere Daten liegen auf unserer lokalen Maschine vor und wir können diese nutzen für die Updates des Models. Federated Learning funktioniert jedoch nicht auf diese Weise.  \n",
    "Lassen Sie uns das Beispiel für das Federated Learning anpassen!\n",
    "\n",
    "Dazu müssen wir:\n",
    "\n",
    "- einige Helfer erstellen\n",
    "- Pointer zu den Trainingsdaten auf jedem Helfer erhalten\n",
    "- die Trainings Infrastruktur aufs Federated Learning anpassen\n",
    "\n",
    "    Neue Trainings Schritte:\n",
    "    - sende das Model zum richtigen Helfer\n",
    "    - trainiere auf den Daten des Helfers\n",
    "    - hole das verbesserte Model zurück und wiederhole alles mit dem nächsten Helfer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:36:16.846632Z",
     "start_time": "2020-03-31T11:36:15.064383Z"
    }
   },
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:36:20.342355Z",
     "start_time": "2020-03-31T11:36:20.334739Z"
    }
   },
   "outputs": [],
   "source": [
    "# create a couple workers\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:36:46.879323Z",
     "start_time": "2020-03-31T11:36:46.855234Z"
    }
   },
   "outputs": [],
   "source": [
    "# A Toy Dataset\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# get pointers to training data on each worker by\n",
    "# sending some training data to bob and alice\n",
    "data_bob = data[0:2]\n",
    "target_bob = target[0:2]\n",
    "\n",
    "data_alice = data[2:]\n",
    "target_alice = target[2:]\n",
    "\n",
    "# Iniitalize A Toy Model\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "data_bob = data_bob.send(bob)\n",
    "data_alice = data_alice.send(alice)\n",
    "target_bob = target_bob.send(bob)\n",
    "target_alice = target_alice.send(alice)\n",
    "\n",
    "# organize pointers into a list\n",
    "datasets = [(data_bob,target_bob),(data_alice,target_alice)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:37:05.927432Z",
     "start_time": "2020-03-31T11:37:05.916019Z"
    }
   },
   "outputs": [],
   "source": [
    "from syft.federated.floptimizer import Optims\n",
    "workers = ['bob', 'alice']\n",
    "optims = Optims(workers, optim=optim.Adam(params=model.parameters(),lr=0.1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:37:46.859285Z",
     "start_time": "2020-03-31T11:37:46.842114Z"
    }
   },
   "outputs": [],
   "source": [
    "def train():\n",
    "    # Training Logic\n",
    "    for iter in range(10):\n",
    "        \n",
    "        # NEW) iterate through each worker's dataset\n",
    "        for data,target in datasets:\n",
    "            \n",
    "            # NEW) send model to correct worker\n",
    "            model.send(data.location)\n",
    "            \n",
    "            #Call the optimizer for the worker using get_optim\n",
    "            opt = optims.get_optim(data.location.id)\n",
    "            #print(data.location.id)\n",
    "\n",
    "            # 1) erase previous gradients (if they exist)\n",
    "            opt.zero_grad()\n",
    "\n",
    "            # 2) make a prediction\n",
    "            pred = model(data)\n",
    "\n",
    "            # 3) calculate how much we missed\n",
    "            loss = ((pred - target)**2).sum()\n",
    "\n",
    "            # 4) figure out which weights caused us to miss\n",
    "            loss.backward()\n",
    "\n",
    "            # 5) change those weights\n",
    "            opt.step()\n",
    "            \n",
    "            # NEW) get model (with gradients)\n",
    "            model.get()\n",
    "\n",
    "            # 6) print our progress\n",
    "            print(loss.get()) # NEW) slight edit... need to call .get() on loss\\\n",
    "    \n",
    "# federated averaging"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-03-31T11:37:47.730112Z",
     "start_time": "2020-03-31T11:37:47.558987Z"
    }
   },
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Gut gemacht!\n",
    "\n",
    "Und voilà! Sie haben ein sehr einfaches Deep Learning Model mittels Federated Learning trainiert! Dabei wurde das Model zu jedem Helfer gesendet, neue Gradienten ermittelt, diese zum Verbessern des Models verwendet und das Model anschließend wieder zum lokalen Server transferiert. Zu keinem Zeitpunkt konnten die zugrundeliegenden Daten dabei direkt eingesehen werden! Die Privatsphäre von Bob und Alice wurde demnach gewahrt!!!\n",
    "\n",
    "## Defizite von diesem Beispiel\n",
    "\n",
    "Auch wenn dieses Beispiel eine gute Einleitung ins Federated Learning darstellt, so hat es doch große Defizite. Vor allem mit dem Aufrufen von `model.get()` und dem Erhalten des verbesserten Models von Bob oder Alice, lässt sich über die Gradienten sehr viel über die Trainingsdaten herausfinden. In einigen Fällen lassen sich die Trainingsdaten sogar komplett wieder herstellen!\n",
    "\n",
    "Wie lässt sich dem entgegenwirken? Die erste Strategie, die häufig eingesetzt wird, ist **über die Gradienten mehrerer Individuen zu mitteln bevor sie als Update zum zentralen Server geschickt werden**. Diese Strategie benötigt jedoch einige anspruchsvollere PointerTensor-Objekte. Deshalb geht es im nächsten Abschnitt um fortgeschrittenere Pointer Funktionalitäten und anschließend wird dieses Federated Learning Beispiel angepasst."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Herzlichen Glückwunsch!!! - Zeit, der Community beizutreten! \n",
    "\n",
    "Herzlichen Glückwunsch zum Abschluss dieses Notebook-Tutorials! Wenn es Ihnen gefallen hat und Sie sich der Bewegung zur Wahrung der Privatsphäre, zum dezentralisiertenen Besitz von KI und der KI-Lieferkette (Daten) anschließen möchten, können Sie dies auf folgende Weise tun! \n",
    "\n",
    "### PySyft auf GitHub einen Stern geben! \n",
    "\n",
    "Der einfachste Weg, unserer Community zu helfen, besteht darin, die GitHub-Repos mit Sternen auszuzeichnen! Dies hilft, das Bewusstsein für die coolen Tools zu schärfen, die wir bauen. \n",
    "\n",
    "- [Gib PySyft einen Stern](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Mach mit bei Slack! \n",
    "\n",
    "Der beste Weg, um über die neuesten Entwicklungen auf dem Laufenden zu bleiben, ist, sich unserer Community anzuschließen! Sie können dies tun, indem Sie das Formular unter [http://slack.openmined.org](http://slack.openmined.org) ausfüllen.\n",
    "\n",
    "### Treten Sie einem Code-Projekt bei! \n",
    "\n",
    "Der beste Weg, um zu unserer Community beizutragen, besteht darin, Entwickler zu werden! Sie können jederzeit zur PySyft GitHub Issues-Seite gehen und nach \"Projects\" filtern. Dies zeigt Ihnen alle Top-Level-Tickets und gibt einen Überblick darüber, an welchen Projekten Sie teilnehmen können! Wenn Sie nicht an einem Projekt teilnehmen möchten, aber ein wenig programmieren möchten, können Sie auch nach weiteren \"einmaligen\" Miniprojekten suchen, indem Sie nach GitHub-Problemen suchen, die als \"good first issue\" gekennzeichnet sind. \n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Spenden\n",
    "\n",
    "Wenn Sie keine Zeit haben, zu unserer Codebase beizutragen, aber dennoch Unterstützung leisten möchten, können Sie auch Unterstützer unseres Open Collective werden. Alle Spenden fließen in unser Webhosting und andere Community-Ausgaben wie Hackathons und Meetups! \n",
    "\n",
    " - [OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
